package com.example.interesting.distributed.lock.realize.aop;

import cn.hutool.core.util.StrUtil;
import com.example.interesting.distributed.lock.realize.DistributedLockTemplate;
import com.example.interesting.distributed.lock.realize.annotation.DistributedLockAnnotation;
import com.example.interesting.distributed.lock.realize.entity.DistributedLockInfo;
import com.example.interesting.distributed.lock.realize.properties.DistributedLockProperties;
import com.example.interesting.distributed.lock.realize.strategy.DefaultDistributedLockFailureStrategy;
import com.example.interesting.distributed.lock.realize.strategy.DefaultDistributedLockKeyBuilder;
import com.example.interesting.distributed.lock.realize.strategy.DistributedLockFailureStrategy;
import com.example.interesting.distributed.lock.realize.strategy.DistributedLockKeyBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author fyw
 * @version 1.0
 * @description: 切点环绕的业务处理逻辑，也就是支持注解式分布式锁的核心实现逻辑
 * <p>
 * MethodInterceptor 是 Spring AOP 中的一个接口，用于实现环绕通知（around advice）。它允许开发者在目标方法执行前后添加自定义逻辑。
 * 主要功能
 * 环绕通知：MethodInterceptor 接口定义了一个方法 invoke，通过实现这个方法可以对目标方法的调用进行拦截并添加额外的行为。
 * 灵活性：环绕通知可以完全控制目标方法的执行，包括在方法执行之前和之后执行特定的操作。
 * <p>
 * InitializingBean 是 Spring 框架中的一个接口，用于执行在 bean 实例化后进行初始化的操作。
 * 主要方法
 * afterPropertiesSet()：这是 InitializingBean 接口中定义的方法。当 bean 的所有属性被设置之后，
 * Spring 容器会自动调用该方法。开发者可以在此方法中编写 bean 的初始化逻辑
 * @date 2024/3/16 8:34
 */
@Slf4j
@RequiredArgsConstructor
public class LockMethodInterceptor implements MethodInterceptor, InitializingBean {

    /**
     * 配置信息
     */
    private final DistributedLockProperties distributedLockProperties;
    /**
     * 分布式锁模板类
     */
    private final DistributedLockTemplate distributedLockTemplate;
    /**
     * 分布式锁默认key生成策略
     */
    private DistributedLockKeyBuilder primaryKeyBuilder;

    /**
     * 分布式锁获取失败处理策略
     */
    private DistributedLockFailureStrategy primaryFailureStrategy;

    /**
     * 分布式锁默认key生成策略 map集合
     */
    private final Map<Class<? extends DistributedLockKeyBuilder>, DistributedLockKeyBuilder> primaryKeyBuilderMap = new LinkedHashMap<>();

    /**
     * 分布式锁获取失败处理策略 map集合
     */
    private final Map<Class<? extends DistributedLockFailureStrategy>, DistributedLockFailureStrategy> primaryFailureStrategyMap = new LinkedHashMap<>();


    /**
     * 分布式锁默认key生成策略 list集合
     */
    private final List<DistributedLockKeyBuilder> primaryKeyBuilders;

    /**
     * 分布式锁获取失败处理策略 list集合
     */
    private final List<DistributedLockFailureStrategy> primaryFailures;


    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //如果AOP代理找到的目标类和当前实际传入的代理类不一样，则直接执行返回，避免其他原因重复代理，重复获取锁
        Class<?> aClass = AopProxyUtils.ultimateTargetClass(invocation.getThis());
        if (!aClass.equals(invocation.getThis().getClass())) {
            return invocation.proceed();
        }
        //获取注解实例信息
        DistributedLockAnnotation lockAnnotation = AnnotatedElementUtils.findMergedAnnotation(invocation.getMethod(),
                DistributedLockAnnotation.class);
        DistributedLockInfo lock = null;
        try {
            //组装key生成器
            DistributedLockKeyBuilder distributedLockKeyBuilder = obtainKeyBuilder(lockAnnotation);
            String key = buildKey(invocation, lockAnnotation, distributedLockKeyBuilder);
            //根据keys构建key
            lock = distributedLockTemplate.lock(key, lockAnnotation.acquireTimeout(),
                    lockAnnotation.expire(), lockAnnotation.executor());
            if (lock != null) {
                return invocation.proceed();
            }
            //锁获取失败处理策略
            DistributedLockFailureStrategy distributedLockFailureStrategy = obtainFailureStrategy(lockAnnotation);
            distributedLockFailureStrategy.onLockFailure(key, invocation.getMethod(), invocation.getArguments(),
                    lockAnnotation.failMsg());
            return null;
        } finally {
            if (lock != null) {
                //锁释放
                boolean releaseResult = distributedLockTemplate.releaseLock(lock);
                if (!releaseResult) {
                    log.error("锁释放失败key={}，method={}，args={}", lock.getName(), invocation.getMethod(), invocation.getArguments());
                } else {
                    log.info("锁释放成功key={}", lock.getName());
                }
            }
        }
    }

    @Override
    public void afterPropertiesSet() {
        primaryKeyBuilderMap.putAll(primaryKeyBuilders.stream().collect(Collectors.toMap(DistributedLockKeyBuilder::getClass, item -> item)));
        primaryFailureStrategyMap.putAll(primaryFailures.stream().collect(Collectors.toMap(DistributedLockFailureStrategy::getClass, item -> item)));
        if (distributedLockProperties.getPrimaryfailureStrategy() != null) {
            primaryFailureStrategy = primaryFailureStrategyMap.get(distributedLockProperties.getPrimaryfailureStrategy());
        } else {
            primaryFailureStrategy = primaryFailureStrategyMap.get(DefaultDistributedLockFailureStrategy.class);
        }
        if (distributedLockProperties.getPrimaryKeyBuilder() != null) {
            primaryKeyBuilder = primaryKeyBuilderMap.get(distributedLockProperties.getPrimaryKeyBuilder());
        } else {
            primaryKeyBuilder = primaryKeyBuilderMap.get(DefaultDistributedLockKeyBuilder.class);
        }
    }

    private DistributedLockKeyBuilder obtainKeyBuilder(DistributedLockAnnotation lockAnnotation) {
        Class<? extends DistributedLockKeyBuilder> aClass = lockAnnotation.keyBuilder();
        if (aClass == null || aClass == DefaultDistributedLockKeyBuilder.class) {
            return primaryKeyBuilder;
        } else {
            return primaryKeyBuilderMap.get(aClass);
        }
    }

    private DistributedLockFailureStrategy obtainFailureStrategy(DistributedLockAnnotation lockAnnotation) {
        Class<? extends DistributedLockFailureStrategy> aClass = lockAnnotation.failureStrategy();
        if (aClass == null || aClass == DefaultDistributedLockFailureStrategy.class) {
            return primaryFailureStrategy;
        } else {
            return primaryFailureStrategyMap.get(aClass);
        }
    }


    private String buildKey(MethodInvocation invocation,
                            DistributedLockAnnotation lockAnnotation,
                            DistributedLockKeyBuilder distributedLockKeyBuilder) {
        //定义结果key
        StringBuilder resultKey = new StringBuilder();
        //前缀
        resultKey.append(distributedLockProperties.getPrefixKey())
                .append(":")
                //name,为空则用方法名
                .append(StrUtil.isBlank(lockAnnotation.name()) ? invocation.getMethod().getDeclaringClass().getName() : lockAnnotation.name())
                .append("#")
                //传入的参数key
                .append(distributedLockKeyBuilder.buildKey(invocation, lockAnnotation.keys()));
        return resultKey.toString();
    }
}
